from django.db import models
from django.utils import timezone
# Встроенный в Django фреймворк аутентификации располагается
# в пакете django.contrib.auth и содержит модель User (Пользователь). Модель
# User будет применяться из указанного фреймворка аутентификации, чтобы
# создавать взаимосвязи между пользователями и постами.
from django.contrib.auth.models import User
# URL-адрес, или URL-указатель (от англ. Uniform Resource Locator, аббр. URL, т. е.
# Унифицированный указатель ресурса), указывает на ресурс в сети. Функция reverse
# в Django выполняет обратное действие и используется для отыскания URL-адреса
# / URL-указателя заданного ресурса 
from django.urls import reverse


from taggit.managers import TaggableManager # Импорт менеджера теггирования

# Создание конкретно-прикладного менеджера

# По умолчанию в каждой модели используется менеджер objects. Этот менед-
# жер извлекает все объекты из базы данных. Однако имеется возможность
# определять конкретно-прикладные модельные менеджеры.
# Давайте создадим конкретно-прикладной менеджер, чтобы извлекать все
# посты, имеющие статус PUBLISHED.
class PublishedManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset()\
                        .filter(status=Post.Status.PUBLISHED)

        # Метод get_queryset() менеджера возвращает набор запросов QuerySet, ко-
        # торый будет исполнен. Мы переопределили этот метод, чтобы сформировать
        # конкретно-прикладной набор запросов QuerySet, фильтрующий посты по их
        # статусу и возвращающий поочередный набор запросов QuerySet, содержащий
        # посты только со статусом PUBLISHED.  НИЖЕ ПРОДОЛЖЕНИЕ (нужно еще объявить созданный менеджер)                      

# Create your models here.
class Post(models.Model): # это модель

    # Добавление поля статуса
    # Очень часто в функциональность ведения блогов входит хранение постов
    # в виде черновика до тех пор, пока они не будут готовы к публикации. Мы
    # добавим в модель поле статуса, которое позволит управлять статусом постов
    # блога. В постах будут использоваться статусы Draft (Черновик) и Published
    # (Опубликован). Их соответствующими значениями выступают DF и PB, а их
    # метками или читаемыми именами являются Draft и Published
    class Status(models.TextChoices): 
        # Мы определили перечисляемый класс Status путем подклассирования
        # класса models.TextChoices
        DRAFT = 'DF', 'Draft'
        PUBLISHED = 'PB', 'Published'

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # Создание полей модели блога
    title = models.CharField(max_length=250) 
    # поле загололвока поста
    
    slug = models.SlugField(max_length=250, unique_for_date='publish') #при использовании параметра unique_for_date поле slug должно быть уникальным для даты, сохраненной в поле publish
    # поле slug для формирования красивых и дружественных для поисковой оптимизации URL-адресов постов блога
    # Слаг – это короткая метка, содержащая только буквы, цифры, знаки подчеркивания или дефисы

    # Добавление взаимосвязи многие-к-одному, 
    # означающую, что каждый пост написан пользователем и 
    # пользователь может написать любое число постов.
    author = models.ForeignKey(User,
                            on_delete=models.CASCADE,
                            related_name='blog_post') 
    # Параметр on_delete определяет поведение, которое следует применять
    # при удалении объекта, на который есть ссылка. Использование
    # ключевого слова CASCADE указывает на то, что при удалении пользователя, на
    # которого есть ссылка, база данных также удалит все связанные с ним посты
    # в блоге.

    # related_name, указывает имя обратной связи, от User
    # к Post. Такой подход позволит легко обращаться к связанным объектам из
    # объекта User, используя обозначение user.blog_posts.
    
    body = models.TextField() 
    # поле для хранения тела поста
    
    publish = models.DateTimeField(default=timezone.now) 
    # поле для хранения даты и времени публикации поста по умодчанию задаваемых методом Django timezone.now
    # Метод timezone.now возвращает текущую дату/время в формате, зависящем от часового пояса.
    
    created = models.DateTimeField(auto_now_add=True)
    # поле для хранения даты и времени создания поста. При применении параметра
    # auto_now_add дата будет сохраняться автоматически во время создания объекта
    
    updated = models.DateTimeField(auto_now=True)
    # поле для хранения последней даты и времени обновления поста. При применении
    # параметра auto_now дата будет обновляться автоматически во время сохранения объекта.
    
    status = models.CharField(max_length=2,
                            choices=Status.choices,
                            default=Status.DRAFT)
    # поле статуса
    # Оно содержит параметр choices, чтобы ограничивать
    # значение поля вариантами из Status.choices. Кроме того, применяя параметр
    # default, задано значение поля, которое будет использоваться по умолчанию.
    # В этом поле статус DRAFT используется в качестве предустановленного вари-
    # анта, если не указан иной.
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    # Создание конкретно-прикладного менеджера (продолжение)
    #     Первый объявленный в модели менеджер становится менеджером, ко-
    # торый используется по умолчанию.
    objects = models.Manager() # Менеджер применяемый по умолчанию
    published = PublishedManager() # Конкретно-прикладной менеджер
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    # Определение предустановленного порядка сортировки  
    # Он будет применяться при извлечении объектов из базы данных, в случае если в запросе порядок не будет указан  
    class Meta:
        
        ordering = ['-publish'] #ordering, сообщает Django, что он должен сортировать результаты по полю publish (знак -: это сортировка в обратном порядке)
        
        indexes = [
            models.Index(fields=['-publish']),
        ]
        # индекс базы данных по полю publish повысит производительность запросов, 
        # фильтрующих или упорядочивающих результаты по указанному полю. 
        # Мы ожидаем, что многие запросы извлекут преимущества из этого индекса, 
        # поскольку для упорядочивания результатов мы по умолчанию используем поле publish.
    
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    tags = TaggableManager() # Добавление функциональности тегирования
    # Далее откройте шаблон blog/post/list.html
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    
    # метод который возвращает строковый литерал с удобочитаемым представлением объекта
    # этот метод для отображения имени объекта во многих местах, таких как его сайт администрирования
    def __str__(self):
        return self.title


    # Взаимодействие с моделью через cmd
    # python manage.py shell

    # Взаимодействие с вариантами статуса
    # >>> from blog.models import Post
    # >>> Post.Status.choices
    # [('DF', 'Draft'), ('PB', 'Published')]

    # >>> Post.Status.labels
    # ['Draft', 'Published']

    # >>> Post.Status.values
    # ['DF', 'PB']

    # >>> Post.Status.names
    # ['DRAFT', 'PUBLISHED']

    # >>> Post.Status.PUBLISHED
    # <Status.PUBLISHED: 'PB'>
    # >>> Post.Status.PUBLISHED.name
    # 'PUBLISHED'
    # >>> Post.Status.PUBLISHED.value
    # 'PB'

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # Работа с наборами запросов QuerySet и менеджерами
    # Создание объектов
    # >>> python manage.py shell
    # >>> from blog.models import Post
    # >>> from blog.models import User
    # >>> user = User.objects.get(username='admin')

    # или так
    # >>> post = Post(title='Another post2', slug='another-post-2', body='Post body2.', author=user)
    # >>> post.save()

    # или так
    # >>> Post.objects.create(title='One more post3', slug='one-more-post-3', body='Post body3.', author=user)
    # Теперь измените заголовок поста на что-то другое и снова сохраните объект:
    # >>> post.title = 'New title3'
    # >>> post.save() #На этот раз метод save() исполняет инструкцию SQL UPDATE.

    #  - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # Извлечение объектов. Применение запросов QuerySet
   
    # Для того чтобы извлечь все объекты из таблицы, используется метод all()
    # применяемого по умолчанию менеджера objects.  
    # >>> all_posts = Post.objects.all()
    # >>> all_posts
    # <QuerySet [<Post: One more post3>, <Post: New title3>, <Post: New Title>, <Post: Мой второй пост>, <Post: Мой первый пост>]>
    
    # Применение метода filter()
    # >>> Post.objects.filter(publish__year=2023)
    # <QuerySet [<Post: One more post3>, <Post: New title3>, <Post: New Title>, <Post: Мой второй пост>, <Post: Мой первый пост>]>
    # Фильтрация также может выполняться по нескольким полям.
    # >>> Post.objects.filter(publish__year=2023, author__username='admin')
    # <QuerySet [<Post: One more post3>, <Post: New title3>, <Post: New Title>, <Post: Мой второй пост>, <Post: Мой первый пост>]>
    # или так
    # >>> Post.objects.filter(publish__year=2023) \
    # >>>                 .filter(author__username='admin')

    # Применение метода exclude()
    # Определенные результаты можно исключать из набора запросов QuerySet,
    # используя метод exclude() менеджера.
    # >>> Post.objects.filter(publish__year=2023) \
    # >>>                 .exclude(title__startswith='Why')
    # <QuerySet [<Post: One more post3>, <Post: New title3>, <Post: New Title>, <Post: Мой второй пост>, <Post: Мой первый пост>]>

    # Применение метода order_by()
    #     Используя метод order_by() менеджера, можно упорядочивать результаты по
    # разным полям. Например, можно извлечь все объекты, упорядоченные по их
    # полю title, как показано ниже:
    # >>> Post.objects.order_by('title')
    # <QuerySet [<Post: New Title>, <Post: New title3>, <Post: One more post3>, <Post: Мой второй пост>, <Post: Мой первый пост>]>
    # >>> Post.objects.order_by('-title')
    # <QuerySet [<Post: Мой первый пост>, <Post: Мой второй пост>, <Post: One more post3>, <Post: New title3>, <Post: New Title>]>

    # Удаление объектов
    # >>> post = Post.objects.get(id=1)
    # >>> post.delete()
    #     Обратите внимание, что удаление объектов также приводит к удалению
    # любых зависимых взаимосвязей объектов ForeignKey, в случае если параметр
    # on_delete задан равным значению CASCADE.

    #  - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # Тестирование конкретно-прикладного менеджера

    #     Теперь можно импортировать модель Post и извлечь все опубликованные
    # посты, заголовки которых начинаются с Who, исполнив следующий ниже на-
    # бор запросов QuerySet:
    # >>> from blog.models import Post
    # >>> Post.published.filter(title__startswith='Who')

    #  - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # Использование канонических
    # URL-адресов для моделей

    # Канонический URL-адрес – это предпочтительный URL-
    # адрес ресурса. Его можно представить как URL-адрес наиболее репрезен-
    # тативной страницы с конкретным контентом. На сайте могут быть разные
    # страницы, которые показывают посты, но есть один URL-адрес, который ис-
    # пользуется в качестве главного URL-адреса поста
    # Django дает возможность в своих собственных моделях реализовывать 
    # метод get_absolute_url(), который возвращает канонический URL-адрес объекта.
    # Мы будем использовать URL-адрес post_detail, определенный в шаблонах
    # URL-адресов приложения, чтобы формировать канонический URL-адрес для
    # объектов Post.

    # Django предоставляет различные функции-резольверы URL-flhtcjd,
    # которые позволяют формировать URL-адреса динамически, исполь-
    # зуя их имя и любые требуемые параметры
    # Резольвер URL-адресов – это программная утилита или функция, которая кон-
    # вертирует логический адрес или метаданные в физический URL-адрес целевых
    # данных.
    # def get_absolute_url(self):
    #     return reverse('blog:post_detail',
    #                     args=[self.id])
    # Функция reverse() будет формировать URL-адрес динамически, применяя
    # имя URL-адреса, определенное в шаблонах URL-адресов. Мы использова-
    # ли именное пространство blog, за которым следуют двоеточие и URL-адрес
    # post_detail.
    # Этот URL-адрес имеет обязательный параметр – id извлекаемого поста бло-
    # га. Идентификатор id объекта Post был включен в качестве позиционного
    # аргумента, используя параметр args=[self.id].
    
    # Меняем для
    # Создание дружественных для поисковой оптимизации
    # URL-адресов постов см. пояснения в models.py
    def get_absolute_url(self):
        return reverse('blog:post_detail',
                        args=[self.publish.year,
                            self.publish.month,
                            self.publish.day,
                            self.slug])

    #  - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # Создание дружественных
    # для поисковой оптимизации
    # URL-адресов постов
    # вместо /blog/1/ будет /blog/2022/1/1/who-was-django-reinhardt/
    # Таким образом мы предоставим поисковым механизмам дружественные для индексации URL-адреса, содержащие
    # как заголовок, так и дату поста
    # Отредактируем файл models.py, добавив следующий ниже параметр
    # unique_for_date в поле slug модели Post
    # Теперь при использовании параметра unique_for_date поле slug должно
    # быть уникальным для даты, сохраненной в поле publish.

    #     Отредактируйте файл urls.py приложения blog, заменив строку
    # path('<int:id>/', views.post_detail, name='post_detail'),
    # строками
    # path('<int:year>/<int:month>/<int:day>/<slug:post>/',
    # views.post_detail,
    # name='post_detail'),

    #     Теперь необходимо видоизменить параметры представления post_detail,
    # чтобы они соответствовали новым параметрам URL-адреса, и использовать
    # их для извлечения соответствующего объекта Post.

    # Далее выполнить Видоизменение представлений

    # Далее выполнить Видоизменение канонического URL-адреса постов

# - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Разработка модели коментария
class Comment(models.Model):
    post = models.ForeignKey(Post,
                            on_delete=models.CASCADE,
                            related_name='comments')
    name = models.CharField(max_length=80)
    email = models.EmailField()
    body = models.TextField()
    created = models.DateTimeField(auto_now_add=True) 
    updated = models.DateTimeField(auto_now=True)
    active = models.BooleanField(default=True)

    class Meta:
        ordering = ['created'] # сортировка
        indexes = [
            models.Index(fields=['created']) # индексация для ускорения поиска 
        ]

    def __str__(self):
        return f'Comment by {self.name} on {self.post}'
    
    #     Это модель Comment. Поле ForeignKey было добавлено для того, чтобы свя-
    # зать каждый комментарий с одним постом. Указанная взаимосвязь многие-
    # к-одному определена в модели Comment, потому что каждый комментарий
    # будет делаться к одному посту, и каждый пост может содержать несколько
    # комментариев.
    # Атрибут related_name позволяет назначать имя атрибуту, который исполь-
    # зуется для связи от ассоциированного объекта назад к нему. Пост коммен-
    # тарного объекта можно извлекать посредством comment.post и все коммен-
    # тарии, ассоциированные с объектом-постом, – посредством post.comments.
    # all(). Если атрибут related_name не определен, то Django будет использовать
    # имя модели в нижнем регистре, за которым следует _set (то есть comment_set),
    # чтобы именовать взаимосвязь ассоциированного объекта с объектом модели,
    # в которой эта взаимосвязь была определена.
    # Подробнее о взаимосвязях многие-к-одному можно узнать на странице
    # https://docs.djangoproject.com/en/4.1/topics/db/examples/many_to_one/.
    # Мы определили булево поле active, чтобы управлять статусом коммен-
    # тариев. Данное поле позволит деактивировать неуместные комментарии
    # вручную с помощью
    # сайта администрирования. Мы используем параметр
    # default=True, чтобы указать, что по умолчанию все комментарии активны.
    # Мы определили поле created, чтобы хранить дату и время создания ком-
    # ментария. Используя auto_now_add, дата будет сохраняться автоматически
    # при создании объекта. В Meta-класс модели был добавлен атрибут ordering
    # = ['created'], чтобы по умолчанию сортировать комментарии в хроноло-
    # гическом порядке и индексировать поля created в возрастающем порядке.

    #     В результате этого будет повышена производительность операций поиска
    # в базе данных и упорядочивания результатов с использованием поля created.
    # Разработанная модель Comment не синхронизирована с базой данных, и по-
    # этому необходимо сгенерировать новую миграцию в базе данных, чтобы
    # создать соответствующую таблицу базы данных и применить ее.

    # Добавление комментариев на сайт администрирования
    # Далее мы добавим новую модель на сайт администрирования, чтобы управ-
    # лять комментариями через простой интерфейс.
    # Откройте файл admin.py приложения blog, импортируйте модель Comment
    # и добавьте следующее:



